home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK2.toast / Development Kits (Disc 2) / OpenDoc Development Framework / Documentation / Engineering Notes / ODF Scripting Overview < prev    next >
Encoding:
Text File  |  1996-09-17  |  23.6 KB  |  308 lines  |  [TEXT/ttxt]

  1. OpenDoc
  2. Development
  3. Framework
  4.                                                                                                                                                                                                  
  5. ODF Scripting Overview
  6. ODF Release  2                                                                                                                                                                         
  7.  
  8. Table of Contents
  9.  
  10. • Introduction
  11. • Changes from ODF 1
  12. • Before Getting Started
  13. • Semantic Interfaces
  14. • Scriptable Objects
  15. • AcquireScriptable/ReleaseScriptable
  16. • Scriptable Objects and Frames
  17. • Accessing Contained Elements
  18. • Properties of Scriptable Objects 
  19. • Automatic Undo of SetProperty
  20. • Scriptable Collections
  21. • Descriptor Records
  22. • Apple Events
  23. • Object Specifiers
  24. • Part Terminology
  25.  
  26.  
  27. Introduction
  28.  
  29. Release 2 of the OpenDoc Development Framework introduces incremental modifications to the ODF Semantic Events subsystem. Acquisition and release semantics of scriptable objects has been improved and clarified, and bugs have been fixed.
  30.  
  31.  
  32. Changes from ODF 1
  33.  
  34. The method FW_MScriptable::GetTokenType was removed, and all support for multiple token types within an ODF part was removed. All tokens representing scriptable objects are now of type FW_kSemanticObjectTokenType.
  35.  
  36. All element accessor methods of FW_MScriptable that began with "Get" have been renamed to begin with "Acquire".  Scriptable objects returned by these methods are now guaranteed to be acquired before being returned.
  37.  
  38. A template FW_TAcquiredScriptableObject was introduced.  An instantiation of this template creates a smart-pointer class that refers to a specific FW_MScriptable object. When the smart-pointer is destroyed, it automatically releases the scriptable object to which it refers.
  39.  
  40. An instantiation of FW_TAcquiredScriptableObject called FW_CAcquiredScriptable was created to refer to FW_MScriptable objects.
  41.  
  42. An instantiation of FW_TAcquiredScriptableObject called FW_CAcquiredScriptableCollection was created to refer to  FW_CScriptableCollection objects.
  43.  
  44. Several reference counting bugs in FW_MScriptable were fixed, and acquisition semantics were cleaned up for consistency.
  45.  
  46. A SaveProperty method was added to FW_MScriptable. This method is called during a set property transaction to allow the scriptable object to provide the current state of the property to be set. If the set property action is undone by the user, the saved property will be passed to RestoreProperty for restoration.
  47.  
  48. Before Getting Started
  49.  
  50. Before getting started, read Chapter 9 of the OpenDoc Programmer’s Guide, “Semantic Events and Scripting”. This chapter provides an overview of how OpenDoc scripting works, and will provide a reference context for the ODF scripting implementation.  This engineering document focuses specifically on the ODF semantic event subsystem and does not attempt to provide a tutorial on scripting support in OpenDoc.
  51.  
  52. Semantic event support in the Macintosh version of OpenDoc is based on the OpenScripting Architecture (OSA) and the Apple Event Manager. This document assumes a working familiarity with OSA concepts and Apple Event Manager data structures such as AEDescs and AppleEvents. A complete reference on this topic is available as “Inside Macintosh: Interapplication Communication,” published by Addison Wesley. Several helpful journal articles are also listed in the section of this document called “Additional Reading Material.”
  53.  
  54.  
  55. Semantic Interfaces
  56.  
  57. Semantic event support in OpenDoc is based on the OpenDoc semantic interface class. When OpenDoc wants to send semantic events to a part, it requests the part’s semantic interface by calling the part’s AcquireExtension method. Scriptable ODF parts automatically create and return an instance of a semantic interface. The ODF semantic interface understands the object hierarchy of ODF parts, and can walk the object tree to locate specific objects. 
  58.  
  59. Semantic events are targeted at specific scriptable objects that live within a part. When OpenDoc receives a semantic event, it first attempts to locate the object or objects at which the event is targeted. Once OpenDoc has resolved the target specifier into an object or a collection of objects, the event is dispatched to the part that contains the target object. During this process the ODF semantic interface acts as an interface between the OpenDoc shell and the objects contained in the target part.
  60.  
  61. ODF provides a fully functional implementation of the OpenDoc Semantic Interface. Scripting can be enabled in  an ODF part by editing its “Defines.k” file.  In its default state,  “Defines.k” contains the following two lines:
  62.  
  63.    #define FW_SUPPORTS_EXTENSIONS     0
  64.    #define FW_SUPPORTS_SCRIPTING     0
  65.  
  66. To enable scripting, edit these lines to read:
  67.  
  68.    #define FW_SUPPORTS_EXTENSIONS     1
  69.    #define FW_SUPPORTS_SCRIPTING     1
  70.  
  71. Building a part with FW_SUPPORTS_SCRIPTING defined as 1 causes ODF to automatically create and return an instance of a Semantic Interface when requested by OpenDoc.  It is necessary to enable support for extensions since ODF’s scripting implementation relies on extension management features enabled by the FW_SUPPORTS_EXTENSIONS definition.
  72.  
  73. The ODF semantic interface is implemented in two parts: a SOM wrapper that is defined in the ODF Shared Library, and a C++ implementation class defined in the Semantic Events subsystem of the ODF Framework layer. When ODF creates a semantic interface, it creates both a wrapper and an implementation object. The wrapper class dispatches directly into the implementation object. The wrapper should never be subclassed or modified. By default, ODF creates an implementation object of class FW_CSemanticInterface. If your part requires a customized subclass of FW_CSemanticInterface, do the following:
  74.  
  75.    1. Declare and implement your subclass of FW_CSemanticInterface
  76.  
  77.    2. Add the following line to your   “Defines.k”  file, replacing   “your_custom_class”
  78.       with the name of your subclass of FW_CSemanticInterface:
  79.  
  80.             #define FW_SEMANTIC_INTERFACE_CLASS  your_custom_class
  81.  
  82.    3. Enable scripting and extensions as explained above.
  83.  
  84.  
  85. Scriptable Objects
  86.  
  87. The Semantic Event subsystem of ODF contains a mixin class, FW_MScriptableObject. Classes that mix FW_MScriptableObject acquire the following behaviors:  1) contained “element” objects can be accessed through the containing scriptable object;  2) properties of the object can be accessed and changed by semantic events; and 3) semantic events can be dispatched directly to the scriptable object once object resolution has been completed. 
  88.  
  89. Scriptable subclasses of FW_CPart should mix in one of the two part-specific subclasses of FW_MScriptable. Embedding parts that are also scriptable should mix FW_MEmbeddingPartScriptable. Non-embedding scriptable parts should mix FW_MPartScriptable. Since the ODF semantic interface makes some assumptions about the behaviors of scriptable parts, it is essential that one of the part-specific subclasses be used in preference to FW_MScriptable.
  90.  
  91. Every scriptable class must have a unique class type constant. AppleScript uses this class type to manage object resolution. When ODF or OpenDoc needs to access the class type of an object, the FW_MScriptable::GetObjectClass method of the object in question is called. GetObjectClass is a virtual method. Subclasses of FW_MScriptable should override this method and return their unique class id.
  92.  
  93.  
  94. Scriptable Objects and Frames
  95.  
  96. Some scripting-related operations require ODF to associate a frame with a scriptable object. Scriptable objects inherit a method called GetFrame from their FW_MScriptable base class that allows them to designate the frame they belong to. By default, this method returns the last active frame of the part. If this default behavior is not appropriate for your scriptable objects, you should override FW_MScriptable::GetFrame and return the correct frame.
  97.  
  98.  
  99. Accessing Contained Elements
  100.  
  101. When OpenDoc receives a semantic event (an Apple event on the Macintosh) it first resolves the target specifier of the event. A target specifier is a tokenized description of a specific object and its relationship to the object hierarchy. In a script, users provide textual descriptions of the objects they want to send events to. AppleScript interprets the textual description, and creates an equivalent tokenized descriptor. An example of an object specifier a user might type into a script is: “shape 1 of part 1”.  Object resolution is a multi-stage process in which OpenDoc walks down the object hierarchy looking for the specified object. At each stage of the traversal, OpenDoc calls the part’s semantic interface asking it to find the next contained element.
  102.  
  103. Classes that mix FW_MScriptable participate in the object resolution process by providing access to the scriptable elements they contain. When ODF wants to access elements of a scriptable object, it calls the AcquireContainedObject method of the containing object. Parameters passed to AcquireContainedObject describe the class of the requested object as well as the method used to specify the object and the specification data.  AcquireContainedObject determines the form used to specify the requested object and calls one of the following accessor methods of FW_MScriptable: AcquireElementByName, AcquireAdjacentObject, AcquireElementsWithinRange,  or AcquireElementByAbsolutePosition.  AcquireElementByAbsolutePosition potentially calls one of the following methods of FW_MScriptable: AcquireFirstElement, AcquireMiddleElement, AcquireLastElement, AcquireAnyElement, AcquireAllElements, or AcquireElementByIndex.
  104.  
  105. FW_MScriptable provides default implementations of all of the object accessor methods listed above. The default implementations of these methods rely on the one access-related method container objects must implement : NewElementIterator. NewElementIterator takes, as a parameter, the object class of the element type ODF is trying to access. NewElementIterator is expected to create and return an instance of a subclass of the abstract base class FW_CElementIterator that iterates over the appropriate objects of the specified class. By implementing this one method, and implementing subclasses of FW_CElementIterator for every scriptable container/element relationship, you enable ODF to provide access to elements via the complete range of specifier forms.
  106.  
  107. ODFDraw provides an example of how this method is implemented. 
  108.  
  109.    FW_CElementIterator* CDrawPart::NewElementIterator(Environment* ev,
  110.          FW_CPart* part,
  111.          ODDescType elementType) const
  112.    {
  113.       FW_CElementIterator* iter;
  114.  
  115.       if (elementType == kShapeClass)
  116.          iter = FW_NEW(CSemanticShapeElementIterator, (fPartContent));
  117.       else
  118.          iter = FW_MEmbeddingPartScriptable::NewElementIterator(ev, part, elementType);
  119.    
  120.       return iter;
  121.    }
  122.  
  123. CDrawPart tests the elementType parameter to determine whether or not the type requested is a type contained by Draw. If it is, a shape iterator is created. If it isn’t, the base class is called to provide the iterator. Iterators returned by NewElementIterator must derive from the abstract base class FW_CElementIterator. The First and Next methods of the iterator class return pointers to FW_MScriptable elements.
  124.  
  125. FW_MScriptable's access of contained objects is always done in terms of element iterators.  This default implementation is written to work with any content model and any hierarchy of containers and elements. While this design is flexible and abstract, it is not as optimal as a design implemented with full knowledge of a specific part's content model would be. If your schedule permits, you might want to investigate overriding some or all of the accessors implemented in FW_MScriptable to provide more optimal access based on knowledge of your part's content model.
  126.  
  127.  
  128. Properties of Scriptable Objects
  129.  
  130. Properties of scriptable objects can be tested and set via semantic events. For example, shapes in the ODFDraw example part have a “color” property.  The color of a shape can be tested and set from AppleScript scripts as in the following example:
  131.  
  132.    tell application “ODFDraw Document”
  133.        -- comment: set the color of the first shape to black
  134.       set the color of the first shape to {0, 0, 0}
  135.    end tell
  136.  
  137. FW_MScriptable contains four methods that are called to get and change the property of a scriptable object: HasProperty, SaveProperty, GetProperty, and SetProperty. When a scriptable ODF part receives a semantic event setting a property of a scriptable object, it first calls the object's HasProperty method to verify that the object has the specified property. ODFDraw implements this method in its shape class, as shown below, to indicate that the color property, accessed in the script above, exists:
  138.  
  139.    FW_Boolean CBaseShape::HasProperty(ODDescType whichProperty) const
  140.    {
  141.       FW_Boolean hasProperty;
  142.  
  143.       if (whichProperty == pColor)
  144.          hasProperty = TRUE;
  145.       else
  146.          hasProperty = FW_MScriptable::HasProperty(whichProperty);
  147.  
  148.       return hasProperty;
  149.    }
  150.  
  151. If the call to HasProperty returns TRUE, ODF calls the SetProperty method of the scriptable object, passing in the information necessary to correctly set the property to the new value. The following example illustrates how this method is implemented for a shape in ODFDraw:
  152.  
  153.    void CBaseShape::SetProperty(Environment* ev,
  154.          FW_CPart* part,
  155.          FW_CDesc& propertyValue,
  156.          ODDescType whichProperty)
  157.    {
  158.       if (whichProperty == pColor)
  159.       {
  160.          CDrawPart* drawPart = FW_DYNAMIC_CAST(CDrawPart, part);
  161.          FW_ASSERT(drawPart);
  162.          FW_CColor newColor;
  163.          
  164.          propertyValue >> newColor;
  165.          ChangeFillColor(ev, drawPart, newColor);
  166.          drawPart->GetDrawContent()->RedrawShape(ev, this);
  167.       }
  168.       else
  169.          FW_MScriptable::SetProperty(ev, part, propertyValue, whichProperty);
  170.    }
  171.  
  172. In the example above, CBaseShape::SetProperty first tests to see whether the property being set is the color property. If some other property is specified, the SetProperty method of the FW_MScriptable base class is invoked. If the color is being set, the “part” parameter is dynamically cast from an FW_CPart* to an CDrawPart* (for information on the FW_DYNAMIC_CAST operator, see the ODF documentation on Run-Time Type Identification). Next, the color value is extracted from the propertyValue parameter (see the section in this document on FW_CDesc for an explanation). Once the new color information is extracted, the shape's color is set, and a message is sent to the part's content object requesting a redraw.
  173.  
  174. ODF accesses property values by calling the GetProperty method of the scriptable object being queried. The following example from ODFDraw illustrates how GetProperty is implemented for a shape's color:
  175.  
  176.    FW_Boolean CBaseShape::GetProperty(Environment* ev,
  177.          FW_CPart* part,
  178.          FW_CDesc& propertyValue,
  179.          ODDescType whichProperty,
  180.          ODDescType desiredType) const
  181.    {
  182.       FW_Boolean result;
  183.  
  184.       if (whichProperty == pColor)
  185.       {
  186.          FW_CColor color = fFillInk.GetForeColor();
  187.          propertyValue << color;
  188.          result = TRUE;
  189.       }
  190.       else
  191.          result = FW_MScriptable::GetProperty(ev, part, propertyValue, 
  192.                         whichProperty, desiredType);
  193.    
  194.       return result;
  195.    }
  196.  
  197.  
  198. Automatic Undo of Set Property
  199.  
  200. ODF provides automatic support for undo/redo of SetProperty semantic events. The steps ODF follows to implement automatic undo are as follows:
  201.  
  202.    1. Call the HasProperty method of the target object to verify the existence of the specified property.
  203.    2. Call the SaveProperty  method of the target object to get the current property value.
  204.    3. Call the GetUndoStrings method of the target object to get the correct undo/redo strings.
  205.    4. Create an undoable/redoable command of type FW_CPropertyCommand.
  206.    5. Execute the command.
  207.  
  208. The two methods mentioned here that haven't already been discussed are FW_MScriptable::SaveProperty and  FW_MScriptable::GetUndoStrings. 
  209.  
  210. The SaveProperty method of FW_MScriptable is called to give the object being modified an opportunity to provide the current state of the property being set. ODF caches this state and, if the action is undone, passes it back to the scriptable object for restoration. The default implementation, provided in FW_MScriptable, of SaveProperty is a call to GetProperty. This behavior will be appropriate for most properties of most scriptable objects. If this behavior needs to be specialized for a specific class, SaveProperty can be overriden.
  211.  
  212. Scriptable objects can optionally override GetUndoStrings to provide strings that are specific to the property being changed. If a scriptable object does not override this method, default strings of “Undo Set Property” and “Redo Set Property” will be used. Below is an example of how the CBaseShape in ODF Draw overrides GetUndoStrings to provide strings specific to changing a color:
  213.  
  214.    void CBaseShape::GetUndoStrings(Environment* ev,
  215.          FW_CPart* part,
  216.          ODDescType whichProperty,
  217.          FW_CString& undoString,
  218.          FW_CString& redoString) const
  219.    {
  220.       if (whichProperty == pColor)
  221.       {
  222.          FW_CSharedLibraryResourceFile resFile(ev);
  223.          ::FW_LoadStringByID(ev, resFile, kDrawUndoStrings, 
  224.                   FW_kMULTISTRINGRES, kUndoFillColorMsg, undoString);
  225.          ::FW_LoadStringByID(ev, resFile, kDrawUndoStrings,
  226.                   kMULTISTRINGRES, kRedoFillColorMsg, redoString);
  227.       }
  228.       else
  229.          FW_MScriptable::GetUndoStrings(ev, part, whichProperty, undoString, redoString);
  230.    }
  231.  
  232. In this example, if the property being set is the color property, undo and redo strings are loaded from the resource fork of the ODF Draw shared library. If the property is not the color property, the FW_MScriptable base class is called to provide the default strings.
  233.  
  234. Since ODF manages the flow of control through this process, the only responsiblity the part writer has is to implement HasProperty, GetProperty, SetProperty, and, optionally, GetUndoStrings, to enable automatic undo of setting of properties.
  235.  
  236. If the user undoes the SetProperty action, a call to FW_MScriptable::RestoreProperty is made, and the saved state originally returned by SaveProperty is passed as a parameter.
  237.  
  238. Scriptable Collections
  239.  
  240. Some semantic events are targeted at multiple scriptable objects. One example of this type of event is generated by the use of “whose” clauses in AppleScript. A whose clause specifies a collection of objects that satisfy a specific test or tests. An example of a whose-claused-based directive a user might write when scripting ODFDraw follows:
  241.  
  242.    tell application "ODFDraw Document"
  243.       -- comment: set the color of every circle to black
  244.       set the color of every shape whose type is circle to {0, 0, 0}
  245.    end
  246.  
  247. OpenDoc and ODF work together to resolve the complex target specifier “every shape whose type is circle”. The result is a collection of scriptable objects, actually CBaseShapes, that satisfy the “type is circle” test.  Whenever ODF needs to create a collection of scriptable objects, it creates an instance of the class FW_CScriptableCollection. FW_CScriptableCollection is a hybrid class: it is both a collection and a scriptable object. This means that every object in the collection must derive from FW_MScriptable. It also means that FW_CScriptableCollection can behave like a scriptable object: it can provide access to its elements, and receive and handle semantic events.  When a semantic event is dispatched to an instance of FW_CScriptableCollection, the collection iterates through its contents dispatching the event to each object in turn.
  248.  
  249.  
  250. Descriptor Records
  251.  
  252. OpenDoc defines a set of classes that are used to pass semantic-event related data into and out of parts. Some of the classes defined by OpenDoc are ODDesc, ODToken, ODAppleEvent and ODObjectSpec.  If you are familiar with the AppleEvent Manager of the Macintosh toolbox, you'll note that the classes defined by OpenDoc closely parallel structures defined by the AppleEvent Manager. The OpenDoc classes are, out of necessity, SOM-based equivalents of the AppleEvent Manager versions.
  253.  
  254. ODF addresses the incompatibility between the OpenDoc and AppleEvent manager versions of descriptor records via a class called FW_CDesc. FW_CDesc can be initialized with either an ODDesc or an AEDesc. Conversion operators exist to provide seamless compatibility with OpenDoc interfaces and with Macintosh toolbox interfaces. In other words, you can pass an FW_CDesc as an AEDesc or an ODDesc and the correct conversions will occur. FW_CDesc uses sophisticated caching to insure that, regardless of how it is passed, data is not stale.
  255.  
  256. In addition to the FW_CDesc interface, ODF provides C++ style insertion and extraction operators for FW_CDesc. These operators are defined as global functions in the source file “FWDscOpr.cp”. In an example that appears earlier in this document, the following lines of code appeared:
  257.  
  258.         
  259.    // property value is an FW_CDesc passed in as a parameter
  260.    FW_CColor newColor;
  261.    propertyValue >> newColor;
  262.  
  263. This example illustrates how a color can be extracted from an FW_CDesc using one of the descriptor extraction operators. The color could also be extracted as follows:
  264.  
  265.    FW_CColor newColor;
  266.    propertyValue.GetColor(newColor);
  267.  
  268. or
  269.  
  270.    FW_CColor newColor;
  271.    RGBColor platformColor;
  272.    Size actualSize;
  273.    GetDataByPtr(typeRGBColor, &platformColor, actualSize, sizeof(RGBColor));
  274.    newColor = platformColor;
  275.  
  276. FW_CDesc's also contain API to enable them to contain AELists and AERecords. List access is supported via the following methods: GetItemCount, GetDescFromList, and DeleteDescFromList. These methods are self explanatory. To support record access, most methods of FW_CDesc take an optional key parameter. If no key is provided, the default “keyNoKey” parameter is used. This internally defined key is recognized by ODF as an indicator that no key should be used to get or set the specified data.
  277.  
  278.  
  279. Apple Events
  280.  
  281. ODF wraps ODAppleEvents inside of a subclass of FW_CDesc called FW_CAppleEvent. FW_CAppleEvent inherits all of the functionality of FW_CDesc. FW_CAppleEvents have additional API that enables access to AppleEvent attributes. ODF provides built-in accessors for AppleEvent class and ID, as well as for subject attributes.
  282.  
  283. While ODF Release 1 does not provide explicit support for recording of AppleEvents, FW_CAppleEvent contains a sample method called FW_MakeSetPropertyEvent, which was written to provide some insight into how a factored, recording part might be written. This method is not currently called by the framework, and is present specifically for its value as sample code.
  284.  
  285.  
  286. Object Specifiers
  287.  
  288. The Semantic Events subsystem of ODF contains a utility method used in the creation of object specifiers. When creating an object specifer, ODF users should call the FW_CreateObjSpecifier method in preference to the similarly named toolbox method, CreateObjSpecifier. FW_CreateObjSpecifier is functionally equivalent to CreateObjSpecifier, but eliminates the need for a part to link against the Macintosh shared library called ObjectSupportLib.
  289.  
  290.  
  291. Part Terminology
  292.  
  293. OpenDoc parts publish their scripting terminology the same way that applications do. Terminology for a part is contained in an resource of type ‘aete’. ODF does not do anything explicit to effect this process. Information on how to create a terminology resource for your part can be found in the OpenDoc Programmer's Guide.
  294.  
  295.  
  296. Additional Reading Material
  297.  
  298. Inside Macintosh: Interapplication Communication (Addison-Wesley, 1993)
  299.  
  300. “Apple Event Objects and You” by Richard Clark, develop  Issue 10
  301.  
  302. “Better Apple Event Coding Through Objects” by Eric M. Berdahl, develop  Issue 12
  303.  
  304. “Designing a Scripting Implementation” by Cal Simone develop  Issue 21
  305.  
  306.  
  307. © 1993 - 1996 Apple Computer, Inc. All rights reserved.
  308. Apple, the Apple Logo, Macintosh, and OpenDoc are trademarks of Apple Computer, Inc., registered in the United States and other countries.